今更という話ではありますが。
JSONPを正しく理解しようと思って、Remote JSON - JSONPを読んだところ、全く意味が理解できず、敗退しました。
というわけで、手間を惜しんだら負けなのかと思い、いい加減で当てにならない日本語訳(後述)をやってみました。しかし、それでも分かりません。
いろいろ苦闘してやっと理解しました。
結論としては、「ソースコードの断片しか提示していないので、全体像が見えないとコードを追えない」「del.icio.us JSON APIの理解を前提としている」「(英文が難しくて読み取れないよ)」という2つ(3つ)の問題が理解を妨げていたことが分かりました。
一応、いい加減な和訳は最後に付けるとして、理解した内容を簡単にまとめておきます。サンプルソースは、原文のものをそのまま使います。
間違い等あれば、ご教示ください。
JSONPってざっくばらんに何? §
JSONデータに、若干の文字(コード)を追加したものです。追加がpaddingで、JSON+Padding=JSONPという感じでしょう。
なぜ追加するのかというと、XMLHttpRequestオブジェクトで受け取ってeval関数で評価するのではなく、HTMLのscript要素で読み込ませたいからです。それによって、別ドメインのデータを持って来ることができます。
具体例を見てみましょう。
サンプルソース §
たとえば、JSONがこうだとします。(サーバからクライアントに送信される内容)
{
"u": "http://mochikit.com/examples/interpreter/index.html",
"d": "Interpreter - JavaScript Interactive Interpreter",
"t": [
"mochikit","webdev","tool","tools",
"javascript","interactive","interpreter","repl"
]
}
しかし、この内容はscript要素では上手く受け取れません。
そこで、受け取るために前後に若干の文字を付けて以下のようにします。(Deliciousを使った一例)(サーバからクライアントに送信される内容)
if(typeof(Delicious) == 'undefined') Delicious = {};
Delicious.posts = [{
"u": "http://mochikit.com/examples/interpreter/index.html",
"d": "Interpreter - JavaScript Interactive Interpreter",
"t": [
"mochikit","webdev","tool","tools",
"javascript","interactive","interpreter","repl"
]
}]
上記の例なら、Delicious.postsで読み込んだデータを参照できます。
いつ読み込みが完了したのか分からないぞ! §
ここまではさほど難しい話ではありません。
難しくなるのは、動的にscript要素を使ってJSONPオブジェクトを要求し、それを受信した場合です。この場合、いつ受信が完了したのか、知る方法がありません。
解決策 §
JSONでは、ストイックにデータに限定して関数などを含めることを許していません。(そうしないと危険だし、他プログラム言語での実装が困難になる)
しかし、既にオブジェクトの生成や代入文を付け足してしまった以上、ストイックさを維持する意味はありません。既に、サーバから戻ってくるデータとは、プログラムそのものなのです。
ならば、転送完了を通知する関数呼び出しのコードを、サーバが返送するデータの一部に含めてしまえばよいのです。
以下は、クライアント側で実行するコールバック付きのサーバ呼び出しコードです。(ただし内容は不完全です。実際にscript要素をいじるコードなど重要な部分が欠落しています)
var delicious_callbacks = {};
function getDelicious(callback, url) {
var uid = (new Date()).getTime();
delicious_callbacks[uid] = function () {
delete delicious_callbacks[uid];
callback();
};
url += "?jsonp=" + encodeURIComponent("delicious_callbacks[" + uid + "]");
// add the script tag to the document, cross fingers
};
getDelicious(doSomething, "http://del.icio.us/feeds/json/bob/mochikit+interpreter");
このgetDelicious関数は、第1引数にコールバックする関数を指定し、第2引数に呼び出すURLを指定します。そして、URLからの読み込みが完了したとき、指定されたコールバック関数が呼び出されます。複数の呼び出しが同時並行で行われても良いように、現在の日付時刻をID(uid)としてコールバック関数を保存しておく仕掛けです。
このとき、サーバから返送されるコードは以下のような関数呼び出しコードになります。ちなみに、12345の部分は動的に変化するuidに対応する部分です。
delicious_callbacks[12345]([{
"u": "http://mochikit.com/examples/interpreter/index.html",
"d": "Interpreter - JavaScript Interactive Interpreter",
"t": [
"mochikit","webdev","tool","tools",
"javascript","interactive","interpreter","repl"
]
}])
動的なscript要素でこれを読み込むと、サーバからの返送値となるオブジェクトを引数として、コールバック関数の呼び出しが発生します。
これにより達成したものは何か §
- 別ドメインのサーバが持つデータの動的な取得
- 読み込み完了の通知
- 標準化された動作
感想 §
私が個人的に思ったことです。
- uidの作成ロジックは本当に妥当か? 1クライアント内でユニークであれば良いのなら、この程度でも良いのか?
- サーバが変なコードを挿入してきたら恐いかも。別ドメインのサーバを呼ぶということは、他人が管理するサーバを利用するということなので、リスクがあるかも
いい加減で当てにならない日本語訳 §
The browser security model dictates that XMLHttpRequest, frames, etc. must have the same domain in order to communicate. That's not a terrible idea, for security reasons, but it sure does make distributed (service oriented, mash-up, whatever it's called this week) web development suck.
ブラウザのセキュリティモデルは、同じドメインと通信を持たねばならないことを、XMLHttpRequest、フレーム、等々に命令する。
セキュリティの理由から、それは恐ろしいアイデアではないが、……(訳注:訳せないが重要ではないと判断して訳さない)
There are traditionally three solutions to solving this problem.
この問題には、伝統的に3つの解決策がある。
Local proxy:
Needs infrastructure (can't run a serverless client) and you get double-taxed on bandwidth and latency (remote - proxy - client).
インフラが必要(サーバ抜きのクライアントでは走らない)で、2重にバンド幅とレイテンシーを使う
Flash:
Remote host needs to deploy a crossdomain.xml file, Flash is relatively proprietary and opaque to use, requires learning a one-off moving target programming langage.
リモートホストは、crossdomain.xmlファイルを配置する必要がある。Flashは相対的に独占的であり、使うには不透明。1回限り動くターゲットのプログラミング言語を習う必要がある。
Script tag:
Difficult to know when the content is available, no standard methodology, can be considered a "security risk".
コンテントが利用可能になったことを知るのが難しく、標準の方法論もなく、セキュリティのリスクとして見なされる。
--------------------------------------------------------------------------------
I'm proposing a new technology agnostic standard methodology for the script tag method for cross-domain data fetching: JSON with Padding, or simply JSONP.
わたしは新しい技術を提案する。ドメインをまたがるデータ取得のためのscriptタグメソッドのための不可知論の標準方法論である。JSON with Padding、あるいはシンプルにJSONPである。
The way JSONP works is simple, but requires a little bit of server-side cooperation. Basically, the idea is that you let the client decide on a small chunk of arbitrary text to prepend to the JSON document, and you wrap it in parentheses to create a valid JavaScript document (and possibly a valid function call).
JSONPの働きはシンプル。しかし、僅かなサーバ側の協力が必要とされる。
基本的に、このアイデアは、前もって考えたJSON文書とする任意のテキストの小さなチャンク上で、あなたがクライアントを決定する。
そして、合法的なJavaScript文書を生成するために、あなたはそれを括弧でラップする。(そして可能なら、合法な関数呼び出しとする)
The client decides on the arbitrary prepended text by using a query argument named jsonp with the text to prepend. Simple! With an empty jsonp argument, the result document is simply JSON wrapped in parentheses.
前もって考えられたテキストと共に、jsonpで名前づけられたクエリ引数によって、クライアントは任意の前もって考えられたテキストを決定する。
シンプル!
空のjsonp引数と共に、括弧の中で、結果文書はシンプルなJSONにラップされている。
Let's take the del.icio.us JSON API as an example. This API has a "script tag" variant that looks like this:
del.icio.us JSON APIを1つの事例としてとりあげよう。
このAPIは、以下のような"script tag"の変種を持っている。
http://del.icio.us/feeds/json/bob/mochikit+interpreter:
if(typeof(Delicious) == 'undefined') Delicious = {};
Delicious.posts = [{
"u": "http://mochikit.com/examples/interpreter/index.html",
"d": "Interpreter - JavaScript Interactive Interpreter",
"t": [
"mochikit","webdev","tool","tools",
"javascript","interactive","interpreter","repl"
]
}]
In terms of JSONP, a document semantically identical to this would be available at the following URL:
JSONPの点から見ると、意味的に等価に文書は以下のURLとして得られるだろう。
http://del.icio.us/feeds/json/bob/mochikit+interpreter?jsonp=if(typeof(Delicious)%3D%3D%27undefined%27)Delicious%3D%7B%7D%3BDelicious.posts%3D
That's not very interesting on its own, but let's say I wanted to be notified when the document is available. I could come up with a little system for tracking them:
それ自身、とても興味深いものではない。
しかし、言わせてほしい。私はいつドキュメントが有効になったのか通知したかった。
私は、それをトラッキングする小さなシステムを仕上げることができた。
var delicious_callbacks = {};
function getDelicious(callback, url) {
var uid = (new Date()).getTime();
delicious_callbacks[uid] = function () {
delete delicious_callbacks[uid];
callback();
};
url += "?jsonp=" + encodeURIComponent("delicious_callbacks[" + uid + "]");
// add the script tag to the document, cross fingers
};
getDelicious(doSomething, "http://del.icio.us/feeds/json/bob/mochikit+interpreter");
The fetched URL from this hypothetical experiment would look something like this:
仮説の実験から来たフェッチURLは以下のように見えるだろう。
http://del.icio.us/feeds/json/bob/mochikit+interpreter?jsonp=delicious_callbacks%5B12345%5D
delicious_callbacks[12345]([{
"u": "http://mochikit.com/examples/interpreter/index.html",
"d": "Interpreter - JavaScript Interactive Interpreter",
"t": [
"mochikit","webdev","tool","tools",
"javascript","interactive","interpreter","repl"
]
}])
See, because we're wrapping with parentheses, a JSONP request can translate into a function call or a plain old JSON literal. All the server needs to do differently is prepend a little bit of text to the beginning and wrap the JSON in parentheses!
見よ、我々は括弧でラップしたために、1つのJSONPリクエストは関数呼び出しに変換でき、あるいはプレーンな古いJSONリテラルでもある。
(2006年9月19日差し替え)(この文は意味が読み取れなかったが、thatを補ってAll that the server needs to do……とすれば意味を読み取れるとメールでご教示頂いた。そこで訳文を差し替えてみた) サーバが異なる動作を行う必要のある全てのことは、開始部分の事前に考えられた小さなテキストとJSONをラップする括弧である。(注: サーバがJSONで送る場合と比較して変更しなければならないのは、JSONデータに括弧を付け、先頭に若干のコードを付けることだけだ……と言っていると推定)
Now, of course, you'd have libraries like MochiKit, Dojo, etc. abstracting JSONP so that you don't have to write the ugly DOM script tag insertion yourself, etc.
今、もちろん、あなたはMochiKitやDojoや他の、JSONPを仮想化するライブラリを既に持っている。そこで、あなたは醜いDOMスクリプトタグ挿入をあなた自身で書く必要はない。等々。
Of course, this just solves the standardization problem. Your page is still toast if the remote host decides to inject malicious code instead of JSON data. However, if implemented, it'd save a lot of developers some time and allow for generic abstractions, tutorials, and documentation to be built.
もちろん、これは標準化の問題を解決する。もし、リモートホストがJSONデータではなく悪意あるコードの挿入を決断したら、あなたのページは依然としてtoastである。
しかしながら、もし実装されていたら、それは多くの開発者のいくばかの時間を節約するだろう。そして、ビルドするための汎用的な仮想化、チュートリアル、文書化が可能になるだろう。